package service

import (
	"encoding/binary"
	"errors"
	"fmt"
	"regexp"
	"serialnet/app"
	"serialnet/config"
	"serialnet/model"
	"serialnet/netty"
	"serialnet/sys/gpio"
	"serialnet/sys/serial"
	"strconv"
	"sync"
	"time"
)

type SerialContext struct {
	Running        bool
	isRs232        bool
	runtimeService *RuntimeService
	LastReceive    int64
	handler        netty.HandlerContext
	handlerMutex   sync.RWMutex
	Para           *model.SerialPara
	paraChanged    bool
	Port           *serial.Port
	ledCtx         *ledContext
}

type ledContext struct {
	ledGpio int
	signal  chan bool
	stop    chan bool
}

func NewSerialContext(is232 bool, runtimeService *RuntimeService) *SerialContext {
	s := &SerialContext{isRs232: is232, runtimeService: runtimeService}
	s.LastReceive = time.Now().Unix()
	return s
}

func (s *SerialContext) Start() error {
	if s.Para == nil {
		return errors.New("serial para is nil")
	}
	if port, err := serial.OpenPort(model.SerialPara2Config(s.Para)); err != nil {
		return err
	} else {
		s.Port = port
		app.Logger.Info(fmt.Sprint("port ", s.Para.Name, "open successful"))
	}
	go s.readSerial()
	return nil
}

func (s *SerialContext) Stop() error {
	s.Running = false
	if s.handler != nil {
		s.handler.Close(nil)
		s.handler = nil
	}
	if s.Port != nil {
		_ = s.Port.Close()
		s.Port = nil
	}
	return nil
}

func (s *SerialContext) RestartPort() {
	defer func() {
		recover()
	}()
	s.Running = false
	if s.Port != nil {
		_ = s.Port.Close()
		s.Port = nil
	}
	_ = s.Start()
}
func (s *SerialContext) SetHandler(handler netty.HandlerContext) {
	s.handlerMutex.Lock()
	defer s.handlerMutex.Unlock()
	if s.handler != nil {
		s.handler.Close(nil)
	}
	s.handler = handler
}
func (s *SerialContext) processMsg(msg *model.MsgFrame) error {
	if msg == nil {
		return errors.New("msg is nil")
	}
	defer func() {
		if err := recover(); err != nil {
			app.Logger.Debug("报文处理异常：", err)
		}
	}()
	if s.ledCtx != nil && len(s.ledCtx.signal) == 0 {
		s.ledCtx.signal <- true
	}
	switch msg.Func {
	case model.MsgTypeTransMsg:
		if len(msg.Data) == 0 {
			return errors.New("data msg is nil")
		}
		_, _ = s.Port.Write(msg.Data)
		return nil
	case model.MsgTypeQueryPara:
		msg.Func = model.MsgTypeQueryParaAck
		msg.Data = make([]byte, 7)
		binary.BigEndian.PutUint32(msg.Data, uint32(s.Para.Baud))
		msg.Data[4] = s.Para.Parity
		msg.Data[5] = s.Para.DataBits
		msg.Data[6] = s.Para.StopBits
		return s.write2Net(msg)
	case model.MsgTypeSetPara:
		if msg.Length != 7 {
			return errors.New("para length error")
		}
		para := &model.SerialPara{LedIO: s.Para.LedIO, ListenPort: s.Para.ListenPort, Name: s.Para.Name}
		baud := binary.BigEndian.Uint32(msg.Data)
		para.Baud = int(baud)
		para.Parity = msg.Data[4]
		para.DataBits = msg.Data[5]
		para.StopBits = msg.Data[6]
		if s.isRs232 {
			if err := config.SaveSerialConfig("rs232", para); err != nil {
				return err
			} else {
				s.Para = para
				app.Config.Rs232Config = s.Para
			}
		} else {
			if err := config.SaveSerialConfig("rs485", para); err != nil {
				return err
			} else {
				s.Para = para
				app.Config.Rs485Config = s.Para
			}
		}

		msg.Func = model.MsgTypeQueryParaAck
		msg.Data = make([]byte, 7)
		binary.BigEndian.PutUint32(msg.Data, uint32(s.Para.Baud))
		msg.Data[4] = s.Para.Parity
		msg.Data[5] = s.Para.DataBits
		msg.Data[6] = s.Para.StopBits
		err := s.write2Net(msg)
		s.RestartPort()
		return err
	case model.MsgTypeTransCtrl:
		return errors.New("not implement")
	default:
		return errors.New("not implement")
	}
}

func (s *SerialContext) readSerial() {
	if s.Running {
		return
	}
	s.Running = true
	defer func() {
		recover()
		if s.Port != nil {
			_ = s.Port.Close()
		}
		s.ledCtx = nil
	}()
	buf := make([]byte, 2000)
	sn := uint32(0)

	// 接收灯闪
	s.ledCtx = &ledContext{ledGpio: 0, signal: make(chan bool), stop: make(chan bool)}
	defer func() {
		if len(s.ledCtx.stop) == 0 {
			s.ledCtx.stop <- true
		}
	}()
	if s.Para != nil {
		regx := regexp.MustCompile("(\\d+)")
		iStr := regx.FindString(s.Para.LedIO)
		if v, e := strconv.ParseUint(iStr, 10, 16); e == nil {
			s.ledCtx.ledGpio = int(v)
		} else {
			s.ledCtx.ledGpio = 0
		}
		go s.ledFlash(s.ledCtx)
	}

	for s.Running {
		if n, err := s.Port.Read(buf); err != nil || n <= 0 {
			time.Sleep(10 * time.Millisecond)
			continue
		} else {
			if len(s.ledCtx.signal) == 0 {
				s.ledCtx.signal <- true
			}
			sn++
			dataMsg := &model.MsgFrame{Start: model.ServerStarter, Func: model.MsgTypeTransMsg, Length: uint16(n), End: model.Ender, Data: buf[:n]}
			_ = s.write2Net(dataMsg)
		}
	}
}

func (s *SerialContext) ledFlash(ctx *ledContext) {
	if ctx == nil {
		app.Logger.Warn("串口LED打开失败:传入参数为空")
		return
	}
	pin, err := gpio.OpenPin(ctx.ledGpio, gpio.ModeOutput)
	if err != nil || pin == nil {
		app.Logger.Warn("串口LED打开失败:", err)
		return
	}
	defer func() {
		pin.Clear()
		_ = pin.Close()
		app.Logger.Warn("串口收发LED处理任务退出")
	}()
	on := false
	for true {
		select {
		case <-ctx.stop:
			return
		case <-ctx.signal:
			if on {
				pin.Clear()
			} else {
				pin.Set()
			}
			on = !on
		case <-time.After(100 * time.Millisecond):
			if on {
				on = !on
				pin.Clear()
			}
			select {
			case <-ctx.stop:
				return
			case <-ctx.signal:
				if on {
					pin.Clear()
				} else {
					pin.Set()
				}
				on = !on
			}
		}
	}
}

func (s *SerialContext) write2Net(msg *model.MsgFrame) (err error) {
	if msg == nil {
		return errors.New("msg is nil")
	}
	if s.handler == nil {
		return errors.New("handler is nil")
	}
	defer func() {
		recover()
	}()
	s.handlerMutex.RLock()
	defer s.handlerMutex.RUnlock()
	err = errors.New("fail to write msg")
	s.handler.Write(msg)
	return nil
}
